#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>

#include "trackerlogger.h"
#include "socketFactory.h"

//extern int errno;
//extern int h_errno;

#define MAXHOSTNAME 100



/*newSocket:cria um novo socket para um  hostname recebido como parametro
parametros: uma struct sockaddr_in vazia que vai ser preenchida pra criar
o novo socket, o tipo do novo socket (SOCK_DGRAM, SOCK_STREAM...)
e a porta que o novo socket vai usar
retorno: retorna um inteiro com o descritor de arquivo
para o socket criado
2: sockaddr_in preenchida*/
int newSocket(int sockType, int port, char *hostname){

    int acceptedSockTypesMask =  (int)SOCK_STREAM | (int)SOCK_DGRAM | (int)SOCK_RAW | (int)SOCK_SEQPACKET |(int)SOCK_RDM ;

        if (port <0){
            logError("newSocket()", "Porta menor que zero" , __FILE__, __LINE__, 0 );
            return PORT_NUMBER_BELOW_ZERO;
        }

        if (hostname == NULL){
                logError("newSocket()", "Hostname é null" , __FILE__, __LINE__, 0 );
                return NULL_HOSTNAME;
        }

        if (sockType<0 || (sockType & acceptedSockTypesMask) <=0 ){
                logError("newSocket()", "Tipo do socket é invalido" , __FILE__, __LINE__, 0 );
                return INVALID_SOCKTYPE;
        }

      struct addrinfo hints, *result;
      int error;

       memset(&hints, 0, sizeof(hints));
       hints.ai_socktype = sockType;
       hints.ai_family = PF_INET;

       memset(&result, 0, sizeof(result));
      /* resolve the domain name into a list of addresses */
      error = getaddrinfo(hostname, NULL, &hints, &result);

      if ( error != 0 ){
          logError("getaddrinfo()", (char*) gai_strerror(error), __FILE__, __LINE__, 0 );
          logStackTrace("newSocket()", __FILE__, __LINE__);
          return ERROR_RESOLVING_HOSTNAME;
      }

      //por algum motivo bizarro a port nao vem setada. entao tem que fazer um cast
      //do sockaddr pra sockaddr_in pra poder seta-la

      struct sockaddr_in *sockAddr;
      sockAddr= (struct sockaddr_in *)( result->ai_addr);
      sockAddr->sin_port=htons(port);

      //get a tcp/ip socket
      int sockFd = socket(PF_INET,sockType,socketProtocol);
      //int sockFd = socket(result->ai_family,result->ai_socktype,result->ai_protocol);

      if (sockFd<0) {
          logError("socket()", "Nao foi possivel abrir o socket" , __FILE__, __LINE__, errno );
          logStackTrace("newSocket()", __FILE__, __LINE__);
          return ERROR_CREATING_SOCKET_FILE_DESCRIPTOR;
      }

    int resConn= connect (sockFd,result->ai_addr, result->ai_addrlen);


    if ( resConn <0){
        logError("connect()", "Nao foi possivel conectar o socket" , __FILE__, __LINE__, errno );
        logStackTrace("newSocket()", __FILE__, __LINE__);
        return ERROR_CONNECTING_FILE_DESCRIPTOR;
    }

    return sockFd;
}


int newUDPSocket(char *hostname, int port ){

    int sockFd = newSocket(SOCK_DGRAM, port, hostname);

    if (sockFd<0) {
        logStackTrace("newUDPSocket()", __FILE__,__LINE__ );
    }

    return sockFd;
}

int newTCPSocket(char *hostname, int port ){

    int sockFd = newSocket(SOCK_STREAM, port, hostname);

    if (sockFd<0) {
        logStackTrace("newTCPSocket()", __FILE__,__LINE__ );
    }

    return sockFd;

}

/*newServerSocket:cria um novo socket, e usa o bind pra setar o localhost
como endereco de internet dele
parametros: uma struct sockaddr_in vazia que vai ser preenchida pra criar
o novo socket, o tipo do novo socket (SOCK_DGRAM, SOCK_STREAM...)
e a porta que o novo socket vai usar
retorno: retorna um inteiro com o descritor de arquivo
para o socket criado*/
int newServerSocket (int socketType, int port){

    struct sockaddr_in sockAddr;
    int acceptedSockTypesMask =  (int)SOCK_STREAM | (int)SOCK_DGRAM | (int)SOCK_RAW | (int)SOCK_SEQPACKET |(int)SOCK_RDM ;

    if (port <0){
             logError("newServerSocket()", "Porta menor que zero" , __FILE__, __LINE__, 0 );
             return PORT_NUMBER_BELOW_ZERO;
    }

    if (socketType<0 || (socketType & acceptedSockTypesMask) <=0 ){
            logError("newServerSocket()", "Tipo do socket é invalido" , __FILE__, __LINE__, 0 );
            return INVALID_SOCKTYPE;
    }

    //get a tcp/ip socket
    int sockFd = socket(PF_INET,socketType,socketProtocol);

    if (sockFd<0) {
        logError("socket()", "Nao foi possivel abrir o socket" , __FILE__, __LINE__, errno );
        logStackTrace("newServerSocket()", __FILE__, __LINE__);
        return ERROR_CREATING_SOCKET_FILE_DESCRIPTOR;
    }

    //limpando addr
    memset(&sockAddr,0,sizeof(sockAddr));

    //setando a familia
    sockAddr.sin_family=PF_INET;
    sockAddr.sin_port=htons(port);
    sockAddr.sin_addr.s_addr=INADDR_ANY;

    //associate process with port
    bind(sockFd, (struct sockaddr *) &sockAddr, sizeof(sockAddr));

    if (sockFd<0) {
       logError("bind()", "Nao foi possivel dar bind no socket" , __FILE__, __LINE__, errno );
       logStackTrace("newServerSocket()", __FILE__, __LINE__);
       return ERROR_BINDING_SOCKET;
    }

    return sockFd;

}

/* a udp server socket */
int newUDPServerSocket(int port){

    int sockFd= newServerSocket(SOCK_DGRAM, port);

    if (sockFd<0) {
       logStackTrace("newUDPServerSocket()", __FILE__, __LINE__);
       return sockFd;
    }

    return sockFd;
}

/*usando fila do listen com tamanho fixo em 5 */
int newTCPServerSocket(int port){

    int sockDescriptor= newServerSocket(SOCK_STREAM, port);

    if (sockDescriptor<0) {
        logStackTrace("newTCPServerSocket()", __FILE__, __LINE__);
        return sockDescriptor;
    }

    int resListen = listen (sockDescriptor, 5);

    if ( resListen< 0){
        logError("listen()", "Nao foi possivel fazer listen com o socket" , __FILE__, __LINE__, errno );
        logStackTrace("newTCPServerSocket()", __FILE__, __LINE__);
        return -1;
    }

    return sockDescriptor;
}


int unblockSocket(int sockDescriptor){

    if ( sockDescriptor< 0){
            logError("unblockSocket()", "Filedescriptor do socket tem numero menor que zero" , __FILE__, __LINE__, 0 );
            return INVALID_FILE_DESCRIPTOR;
    }


    /* Explicacao sobre como fazer IO nao bloqueante retirada do site da IBM:
    http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.basetechref/doc/basetrf1/ioctl32.htm

    The third parameter to the ioctl subroutine for this command is a pointer to an integer that indicates whether nonblocking I/O is being enabled or disabled. A value of 0 disables non-blocking I/O. Any nonzero value enables nonblocking I/O. A sample code fragment follows:

    int flag;
    enable NBIO for fd1
    flag = 1;
    ioctl(fd1,FIONBIO,&flag);

    disable NBIO for fd2
    flag = 0;
    ioctl(fd2,FIONBIO,&flag);

    sei la por que raio o ioctl precisa de um ponteiro pra int como 3o argumento, mas enfim...
    */

    int flag=1, resioctl=-1;

    resioctl = ioctl(sockDescriptor,FIONBIO,&flag);

    if ( resioctl< 0){
        logError("ioctl()", "Nao foi possivel desbloquear o socket" , __FILE__, __LINE__, errno );
        logStackTrace("unblockSocket()", __FILE__, __LINE__);
        return -1;
    }

    return 0;

}

int newUnblockingUDPSocket(char *hostname, int port ){

    int sockDescr= newUDPSocket(hostname, port);

    if (sockDescr<0) {
        logStackTrace("newUnblockingUDPSocket()", __FILE__, __LINE__);
        return -1;
    }

    int resUnblock =unblockSocket(sockDescr);

    if (resUnblock<0) {
       logStackTrace("newUnblockingUDPSocket()", __FILE__, __LINE__);
       return -1;
    }

    return sockDescr;

}

int newUnblockingTCPSocket(char *hostname, int port ){

    int sockDescr= newTCPSocket(hostname, port );
    if (sockDescr<0) {
        logStackTrace("newUnblockingTCPSocket()", __FILE__, __LINE__);
       return -1;
    }

    int resUnblock =unblockSocket(sockDescr);
    if (resUnblock<0) {
       logStackTrace("newUnblockingTCPSocket()", __FILE__, __LINE__);
       return -1;
    }
    return sockDescr;
}


int newUnblockingUDPServerSocket(int port){

    int sockDescr= newUDPServerSocket(port);
    if (sockDescr<0) {
       logStackTrace("newUnblockingTCPSocket()", __FILE__, __LINE__);
       return -1;
    }

    int resUnblock = unblockSocket(sockDescr);
    if (resUnblock<0) {
        logStackTrace("newUnblockingUDPServerSocket()", __FILE__, __LINE__);
        return -1;
    }

    return sockDescr;

}

int newUnblockingTCPServerSocket( int port){

    int sockDescr= newTCPServerSocket( port);

    if (sockDescr<0) { logStackTrace("newUnblockingTCPSocket()", __FILE__, __LINE__); return -1; }

    int resUnblock = unblockSocket(sockDescr);

    if (resUnblock<0) {logStackTrace("newUnblockingTCPServerSocket()", __FILE__, __LINE__); return -1; }

    return sockDescr;
}

